package javafxtest;

import com.sun.javafx.perf.PerformanceTracker;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Shape3D;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;

public class JavaFXStressTest extends Application {
    private final long[] frameTimes = new long[100];
    private int frameTimeIndex = 0 ;
    private boolean arrayFilled = false ;

    private int totalNodes = 0;
    private int totalGeometry = 0;
    final PerspectiveCamera camera = new PerspectiveCamera(true);

    double mousePosX;
    double mousePosY;
    double mouseOldX;
    double mouseOldY;
    double mouseDeltaX;
    double mouseDeltaY;

    final Rotate rotateX = new Rotate(0, Rotate.Y_AXIS);
    final Rotate rotateY = new Rotate(0, Rotate.X_AXIS);

    final Rotate rotate = new Rotate(10, Rotate.Z_AXIS);

    final private Box box = new Box(2f, 0.5f, 0.5f);

    private void initCamera() {
        camera.setNearClip(1);
        camera.setFarClip(1000.0);
        camera.setFieldOfView(45);
        camera.setVerticalFieldOfView(true);

        camera.getTransforms().addAll(rotateX, rotateY);
        /*
        camera.location = Vector3f(400.8009f, 370.16455f, -408.17984f)
        camera.rotation = Quaternion(0.24906662f, -0.3756747f, 0.105560325f, 0.88639235f)
         */
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        final var fpsText = new Text(10, 20, "FPS: 0");
        fpsText.setFill(Color.WHITE);
        AnimationTimer frameRateMeter = new AnimationTimer() {
            @Override
            public void handle(long now) {
                rotate.setAngle(rotate.getAngle() + 1);
                long oldFrameTime = frameTimes[frameTimeIndex] ;
                frameTimes[frameTimeIndex] = now ;
                frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length ;
                if (frameTimeIndex == 0) {
                    arrayFilled = true ;
                }
                if (arrayFilled) {
                    long elapsedNanos = now - oldFrameTime ;
                    long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
                    double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
                    fpsText.setText(String.format("Current frame rate: %.3f", frameRate));
                }
            }
        };
        frameRateMeter.start();

        initCamera();

        var oct = createOctSplit("root", 500, 5);
        System.out.println(String.format("Total nodes: %d  Total Geometry:%s", totalNodes, totalGeometry));

        // 创建一个SubScene
        Group subRoot = new Group(oct);

//        subRoot.getTransforms().addAll(rotateX, rotateY);

        SubScene subScene = new SubScene(subRoot, 640, 480);
        subScene.setFill(Color.BLACK);

        subScene.setCamera(camera);

        // 将SubScene放置在主场景上
        Group root = new Group(subScene);
        Scene scene = new Scene(root, 640, 480);
        primaryStage.setScene(scene);

//        // 设置旋转动画
//        Rotate rotate = new Rotate(0, Rotate.Y_AXIS);
//        box.getTransforms().add(rotate);
//        subRoot.getTransforms().add(new Translate(150, 150, 0));

        // 添加FPS显示文本
        root.getChildren().add(fpsText);
        handleMouse(scene);

        primaryStage.setTitle("Hello JAVAFX!");
        primaryStage.show();
    }

    private void handleMouse(Scene scene) {
        scene.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent me) {
                mousePosX = me.getSceneX();
                mousePosY = me.getSceneY();
                mouseOldX = me.getSceneX();
                mouseOldY = me.getSceneY();
            }
        });
        scene.setOnMouseDragged(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent me) {
                mouseOldX = mousePosX;
                mouseOldY = mousePosY;
                mousePosX = me.getSceneX();
                mousePosY = me.getSceneY();
                mouseDeltaX = (mousePosX - mouseOldX);
                mouseDeltaY = (mousePosY - mouseOldY);

                double modifier = 1.0;
                if (me.isSecondaryButtonDown()) {
                    camera.setTranslateZ(modifier * mouseDeltaX + camera.getTranslateZ());
                }
                else {
                    rotateX.setAngle(rotateX.getAngle() + modifier*mouseDeltaX);
                    rotateY.setAngle(rotateY.getAngle() + modifier*mouseDeltaY);
                }
            }
        });
    }

    protected Node createOctSplit(String name, int size, int depth) {
        //do not share box
        var box = new Box(2.0f, 0.5f, 0.5f);
        if( depth == 0 ) {
            // Done splitting
            Shape3D geom = box;
            totalGeometry++;
            final PhongMaterial redMaterial = new PhongMaterial();
            redMaterial.setDiffuseColor(Color.color(Math.random(), Math.random(), Math.random()));
            geom.setMaterial(redMaterial);

            if( Math.random() < 0.01 ) {
                geom.getTransforms().add(rotate);
            }

            return geom;
        }

        Group root = new Group();
        root.setAccessibleHelp(name);
        totalNodes++;

        int half = size / 2;
        float quarter = half * 0.5f;

        for( int i = 0; i < 2; i++ ) {
            float x = i * half - quarter;
            for( int j = 0; j < 2; j++ ) {
                float y = j * half - quarter;
                for( int k = 0; k < 2; k++ ) {
                    float z = k * half - quarter;
                    Node child = createOctSplit(name + "(" + i + ", " + j + ", " + k + ")",
                            half, depth - 1);
                    child.setTranslateX(x);
                    child.setTranslateY(y);
                    child.setTranslateZ(z);
                    root.getChildren().add(child);
                }
            }
        }

        return root;
    }

}
